# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: jaewon

import datetime

from coin.exchange.binance.kr_rest.product import BinanceProduct
from coin.exchange.bitflyer_v1.kr_rest.futures_product import BitflyerFuturesProduct
from coin.exchange.bitmex.kr_rest.futures_product import BitmexFuturesProduct
from coin.exchange.okex_futures.kr_rest.futures_product import OkexFuturesProduct

product_to_str = {
    # BTC
    BitmexFuturesProduct.FromStr('BTC-USD.PERPETUAL'): 'xbtusd',
    OkexFuturesProduct.FromStr('BTC-USD.QUARTER'): 'btc-quarter',
    BitflyerFuturesProduct.FromStr('BTC-JPY.PERPETUAL'): 'fxbtcjpy',
    BinanceProduct.FromStr('BTC-USDT'): 'btcusdt-binance',
    # ETH
    BitmexFuturesProduct.FromStr('ETH-USD.PERPETUAL'): 'ethusd',
    OkexFuturesProduct.FromStr('ETH-USD.QUARTER'): 'eth-quarter',
    BinanceProduct.FromStr('ETH-USDT'): 'ethusdt-binance'
}
str_to_product = {v: k for k, v in product_to_str.items()}


def get_str_from_product(product):
  if isinstance(product, str):
    assert product in str_to_product, 'Unknown product: %s' % product
    return product
  else:
    assert product in product_to_str, 'Unknown product: %s' % str(product)
    return product_to_str[product]


def compose_param_set_name(param_set):
  # Example name:
  #   pass_unhedge.xbtusd.btc-quarter.30m.5bp.5bp.12stack.15000
  comps = ['pass_unhedge']
  comps.append(get_str_from_product(param_set['trade_product']))
  comps.append(get_str_from_product(param_set['ref_product']))
  comps.append('%02.0fm' % param_set['basis_ma_window_min'])
  comps.append('%02.0fbp' % param_set['edge_bp'])
  comps.append('%02.0fbp' % param_set['close_edge_bp'])
  comps.append('%02.0fstack' % param_set['stack'])
  comps.append('%.0f' % param_set['lot_size'])
  return '.'.join(comps)


def decompose_param_set_name(name):
  param_set = {}
  comps = name.split('.')

  assert comps[0] == 'pass_unhedge'
  param_set['trade_product'] = str_to_product[comps[1]]
  param_set['ref_product'] = str_to_product[comps[2]]

  assert comps[3][-1:] == 'm'
  param_set['basis_ma_window_min'] = int(comps[3][:-1])

  assert comps[4][-2:] == 'bp'
  param_set['edge_bp'] = float(comps[4][:-2])

  assert comps[5][-2:] == 'bp'
  param_set['close_edge_bp'] = float(comps[5][:-2])

  assert comps[6][-5:] == 'stack'
  param_set['stack'] = int(comps[6][:-5])

  param_set['lot_size'] = int(comps[7])
  return param_set


def get_pass_unhedge_name(strategy_params):
  lot_size = strategy_params['pass_executor']['lot_size']
  max_pos = strategy_params['pass_executor']['max_pos']
  stack = 2 * max_pos // lot_size

  param_set = {
      'trade_product': strategy_params['trade_product'],
      'ref_product': strategy_params['ref_product'],
      'basis_ma_window_min': strategy_params['pricing']['basis_ma_window'] / (60 * 10**9),
      'edge_bp': strategy_params['edge_bp'],
      'close_edge_bp': strategy_params['close_edge_bp'],
      'stack': stack,
      'lot_size': lot_size
  }
  return param_set


def get_true_book_funcs(product):
  product_xbtusd = BitmexFuturesProduct.FromStr('BTC-USD.PERPETUAL')
  product_btc_quarter = OkexFuturesProduct.FromStr('BTC-USD.QUARTER')
  product_fxbtcjpy = BitflyerFuturesProduct.FromStr('BTC-JPY.PERPETUAL')
  product_btcusdt_binance = BinanceProduct.FromStr('BTC-USDT')

  product_ethusd = BitmexFuturesProduct.FromStr('ETH-USD.PERPETUAL')
  product_eth_quarter = OkexFuturesProduct.FromStr('ETH-USD.QUARTER')
  product_ethusdt_binance = BinanceProduct.FromStr('ETH-USDT')

  if product == product_xbtusd:
    true_book_funcs = ((lambda book: book.get_notional_asks_by_qty(10000.)[1][-1][0]),
                       (lambda book: book.get_notional_bids_by_qty(10000.)[1][-1][0]))

  elif product == product_btc_quarter:
    true_book_funcs = ((lambda book: book.get_notional_asks_by_qty(100.)[1][-1][0]),
                       (lambda book: book.get_notional_bids_by_qty(100.)[1][-1][0]))

  elif product == product_fxbtcjpy:
    true_book_funcs = ((lambda book: book.get_notional_asks_by_qty(1.)[1][-1][0]),
                       (lambda book: book.get_notional_bids_by_qty(1.)[1][-1][0]))

  elif product == product_btcusdt_binance:
    true_book_funcs = ((lambda book: book.get_notional_asks_by_qty(1.)[1][-1][0]),
                       (lambda book: book.get_notional_bids_by_qty(1.)[1][-1][0]))

  elif product == product_ethusd:
    true_book_funcs = ((lambda book: book.get_notional_asks_by_qty(1000.)[1][-1][0]),
                       (lambda book: book.get_notional_bids_by_qty(1000.)[1][-1][0]))

  elif product == product_eth_quarter:
    true_book_funcs = ((lambda book: book.get_notional_asks_by_qty(1000.)[1][-1][0]),
                       (lambda book: book.get_notional_bids_by_qty(1000.)[1][-1][0]))

  elif product == product_ethusdt_binance:
    true_book_funcs = ((lambda book: book.get_notional_asks_by_amt(10000.)[1][-1][0]),
                       (lambda book: book.get_notional_bids_by_amt(10000.)[1][-1][0]))

  else:
    raise ValueError('Not supported: %s' % str(product))

  return true_book_funcs


def get_param_set(name):
  product_xbtusd = BitmexFuturesProduct.FromStr('BTC-USD.PERPETUAL')
  product_btc_quarter = OkexFuturesProduct.FromStr('BTC-USD.QUARTER')
  product_fxbtcjpy = BitflyerFuturesProduct.FromStr('BTC-JPY.PERPETUAL')
  product_btcusdt_binance = BinanceProduct.FromStr('BTC-USDT')

  product_ethusd = BitmexFuturesProduct.FromStr('ETH-USD.PERPETUAL')
  product_eth_quarter = OkexFuturesProduct.FromStr('ETH-USD.QUARTER')
  product_ethusdt_binance = BinanceProduct.FromStr('ETH-USDT')

  # ETH
  if name == 'eth-15m11':
    return {
        'og_config': 'bitmex_xunke15',
        'trade_product': product_ethusd,
        'ref_product': product_eth_quarter,
        'basis_ma_window_min': 15,
        'edge_bp': 11.,
        'close_edge_bp': 5.,
        'stack': 10,
        'lot_size': int(1000000. / 8000. * 8.0),  # 8.0 delta
    }

  elif name == 'eth-02m11':
    return {
        'og_config': 'bitmex_xunke16',
        'trade_product': product_ethusd,
        'ref_product': product_eth_quarter,
        'basis_ma_window_min': 2,
        'edge_bp': 11.,
        'close_edge_bp': 11.,
        'stack': 10,
        'lot_size': int(1000000. / 8000. * 8.0),  # 1.0 delta
    }

  elif name == 'eth-05m11':
    return {
        'og_config': 'bitmex_xunke19',
        'trade_product': product_ethusd,
        'ref_product': product_eth_quarter,
        'basis_ma_window_min': 5,
        'edge_bp': 11.,
        'close_edge_bp': 11.,
        'stack': 10,
        'lot_size': int(1000000. / 8000. * 8.0),  # 1.0 delta
    }

  elif name == 'eth-05m13':
    return {
        'og_config': 'bitmex_xunke20',
        'trade_product': product_ethusd,
        'ref_product': product_eth_quarter,
        'basis_ma_window_min': 5,
        'edge_bp': 13.,
        'close_edge_bp': 13.,
        'stack': 10,
        'lot_size': int(1000000. / 8000. * 8.0),  # 1.0 delta
    }

  elif name == 'eth-30m15':
    return {
        'og_config': 'bitmex_xunke17',
        'trade_product': product_ethusd,
        'ref_product': product_eth_quarter,
        'basis_ma_window_min': 30,
        'edge_bp': 15.,
        'close_edge_bp': 15.,
        'stack': 10,
        'lot_size': int(1000000. / 8000. * 8.0),  # 1.0 delta
    }
  elif name == 'eth-30m17':
    return {
        'og_config': 'bitmex_xunke18',
        'trade_product': product_ethusd,
        'ref_product': product_eth_quarter,
        'basis_ma_window_min': 30,
        'edge_bp': 17.,
        'close_edge_bp': 17.,
        'stack': 10,
        'lot_size': int(1000000. / 8000. * 8.0),  # 1.0 delta
    }

  elif name == 'test':
    return {
        'og_config': 'bitmex_xunke03',
        'trade_product': product_xbtusd,
        'ref_product': product_btc_quarter,
        'basis_ma_window_min': 5,
        'edge_bp': 6.,
        'close_edge_bp': 2.,
        'stack': 8,
        'lot_size': 50
    }

  else:
    return decompose_param_set_name(name)


def get_strategy_params(param_set_name,
                        group=None,
                        tag=None,
                        og_config=None,
                        init_og_immediately=False):
  param_set = get_param_set(param_set_name)
  assert param_set

  bitmex_og_config = param_set.get('og_config', None)
  if og_config is not None:
    bitmex_og_config = og_config

  trade_product = param_set['trade_product']
  trade_product_true_book_funcs = get_true_book_funcs(trade_product)
  ref_product = param_set['ref_product']
  ref_product_true_book_funcs = get_true_book_funcs(ref_product)
  ref_product_price_multiplier = param_set.get('ref_price_multiplier', 1.)

  basis_ma_window_min = param_set['basis_ma_window_min']
  edge_bp = param_set['edge_bp']
  close_edge_bp = param_set['close_edge_bp']
  agg_edge_bp = param_set.get('agg_edge_bp', None)

  stack = param_set['stack']
  assert stack % 2 == 0
  half_stack = stack // 2
  lot_size = param_set['lot_size']
  min_pos = -lot_size * half_stack
  max_pos = lot_size * half_stack

  name = param_set.get('name', compose_param_set_name(param_set))
  if tag:
    name = '%s-%s' % (name, tag)

  enable_queue0_executor = False
  enable_stop_loss_executor = False

  if trade_product == BitmexFuturesProduct.FromStr('BTC-USD.PERPETUAL'):
    tick_size = 0.5
  elif trade_product == BitmexFuturesProduct.FromStr('ETH-USD.PERPETUAL'):
    tick_size = 0.05
  else:
    raise ValueError('Unknown product: %s' % str(trade_product))

  logger_group = None
  if name.startswith('pass_unhedge.ethusd.eth-quarter'):
    logger_group = 'pass_unhedge.ethusd.eth-quarter'
  elif name.startswith('pass_unhedge.xbtusd.btc-quarter'):
    logger_group = 'pass_unhedge.xbtusd.btc-quarter'

  NS_PER_MINUTE = 60 * (10**9)
  strategy_params = {
      'name': name,
      'group': group,
      'account': bitmex_og_config,
      'trade_product': trade_product,
      'ref_product': ref_product,
      'edge_bp': edge_bp,
      'close_edge_bp': close_edge_bp,
      'agg_edge_bp': agg_edge_bp,
      'min_pos': min_pos,
      'max_pos': max_pos,
      'order_update_period': 2.,
      'enable_queue0_executor': enable_queue0_executor,
      'enable_stop_loss_executor': enable_stop_loss_executor,
      'pricing': {
          'basis_ma_window': basis_ma_window_min * NS_PER_MINUTE,
          'tick': tick_size,
          'book_askt_func_1': ref_product_true_book_funcs[0],
          'book_bidt_func_1': ref_product_true_book_funcs[1],
          'book_askt_func_2': trade_product_true_book_funcs[0],
          'book_bidt_func_2': trade_product_true_book_funcs[1],
          'price_multiplier_1': ref_product_price_multiplier,
      },
      'pass_executor': {
          'product': trade_product,
          'lot_size': lot_size,
          'stickiness': 0.,
          'min_pos': min_pos,
          'max_pos': max_pos,
          'post_only': True,
      },
      'queue0_executor': {
          'product': trade_product,
          'lot_size': lot_size,
          'min_pos': min_pos,
          'max_pos': max_pos,
          'post_only': True,
      },
      'stop_loss_executor': {
          'product': trade_product,
          'qty_add': lot_size,
          'trigger_threshold': 70. / 10000.,
          'execution_payup': 90. / 10000.
      },
      'agg_executor': {
          'product': trade_product,
          'default_lot_size': lot_size,
          'min_pos': min_pos,
          'max_pos': max_pos,
          'exclude_on_the_fly': True,
      },
      'logger': {
          'strategy_name': name, 'strategy_group': logger_group, 'trader': 'jaewon'
      }
  }

  strategy_params['logger']['strategy_name'] = strategy_params['logger']['strategy_name'].replace(
      '.', '_')
  strategy_params['logger']['strategy_group'] = strategy_params['logger']['strategy_group'].replace(
      '.', '_')

  # Trading environment config
  if init_og_immediately:
    init_og_after_min = 0
  else:
    init_og_after_min = basis_ma_window_min + 0.1

  exit_after_min = init_og_after_min + 30 - 0.1
  env_config = {
      'og_config_name': bitmex_og_config,
      'init_order_gateway_after': datetime.timedelta(minutes=init_og_after_min),
      'exit_after': datetime.timedelta(minutes=exit_after_min)
  }

  return strategy_params, env_config
